using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Diagnostics;
using GoodStuff;

namespace Octopus111
{
    public class MovieWriter : WorkerThread , ISampleProcessor
    {
        GoodConvertibleVariable<uint> _framesToCapture;
        GoodVariable<EventHandler> _start;
        GoodVariable<EventHandler> _stop;
        GoodVariable<string> _outputDirectory;
        GoodVariable<string> _movieName;
        GoodConvertibleVariable<uint> _movieNumber;
        GoodVariable<bool> _autoincrementName;
        GoodVariable<string> _movieFrameElement;

        GoodVariable<uint> _framesCaptured;
        ManualResetEvent _capturing;

        ThreadSafeQueue<Sample> _frameQueue;

        FileStream _frameInfoFileSream;
        StreamWriter _frameInfoStreamWriter;

        string _currentMoviePath;
        string _currentMovieName;

        public MovieWriter()
        {
            _frameQueue = new ThreadSafeQueue<Sample>(300);
            _capturing = new ManualResetEvent(true);

            _framesToCapture = new GoodConvertibleVariable<UInt32>();
            _framesToCapture.InstanceName = "FramesToCapture";
            _framesToCapture.Value = 300;
            StateStore.Store.AddVariable(_framesToCapture);

            _start = new GoodVariable<EventHandler>();
            _start.InstanceName = "StartCapture";
            StateStore.Store.AddVariable(_start);

            _stop = new GoodVariable<EventHandler>();
            _stop.InstanceName = "StopCapture";
            _stop.Value = new EventHandler(StopCapture);
            StateStore.Store.AddVariable(_stop);

            _outputDirectory = new GoodVariable<string>();
            _outputDirectory.InstanceName = "MovieOutputDirectory";
            StateStore.Store.AddVariable(_outputDirectory);

            _movieName = new GoodVariable<string>();
            _movieName.InstanceName = "MovieName";
            _movieName.Value = "Movie";
            StateStore.Store.AddVariable(_movieName);

            _movieNumber = new GoodConvertibleVariable<uint>();
            _movieNumber.InstanceName = "MovieNumber";
            _movieNumber.Value = 0;
            StateStore.Store.AddVariable(_movieNumber);

            _autoincrementName = new GoodVariable<bool>();
            _autoincrementName.InstanceName = "AutoincrementMovieName";
            _autoincrementName.Value = true;
            StateStore.Store.AddVariable(_autoincrementName);

            _movieFrameElement = new GoodVariable<string>();
            _movieFrameElement.InstanceName = "MovieFrameElement";
            _movieFrameElement.Value = "Bitmap 24BppRgb";
            StateStore.Store.AddVariable(_movieFrameElement);

            _framesCaptured = new GoodConvertibleVariable<uint>();
            _framesCaptured.InstanceName = "FramesCaptured";
            _framesCaptured.Value = _framesToCapture.Value;
            StateStore.Store.AddVariable(_framesCaptured);
        }

        override protected void ThreadProc()
        {
            while (KeepGoing(100))                                           // loop until quit signal
            {
                _frameQueue.Wait(100);                                       // wait for a frame to be queued

                while (_frameQueue.Count > 0)                                // loop until all queued frames processed
                {
                    Sample f = _frameQueue.Dequeue();                         // get the next frame

                    if (_framesCaptured.Value == 0)                                // starting a new movie
                        StartMovieCapture();                                 // write movie info and open frame info stream

                    if (_framesCaptured.Value < _framesToCapture.Value)            // movie capture in progress
                        WriteFrame(f);                                       // write out a .bmp file

                    if ((_framesCaptured.Value >= _framesToCapture.Value) &&       // movie capture ending
                        (_frameInfoStreamWriter != null))
                        EndMovieCapture();                                   // close all the open streams

                    if (FrameComplete != null)
                        FrameComplete(this, new SampleAcquiredEventArgs(f));  // pass frame on to next object for processing
                    else
                        f = null;                                            // dispose of the frame
                }                                                            // end of while(_frames.Count > 0)
            }                                                                // end of while(KeepGoing())

            EndMovieCapture();                                               // make sure file streams are close before quitting
        }

        private void StartMovieCapture()
        {
            // let everybody know a movie is in progress
            _capturing.Reset();

            // write out the movie parameters
            string fn;
            fn = _currentMoviePath + @"\" + _currentMovieName + @" info" + @".txt";
            FileStream fs = new FileStream(fn, FileMode.Create);
            StreamWriter sw = new StreamWriter(fs);
            sw.WriteLine(
                "Capture Started at: " + DateTime.Now.ToString()); /*+
                " Sample Period: " +
                    (StateStore.Store["Sample Period"] as GoodNumericVariable<int>).ToString() +
                    "ms"+
                " Dimensions: " +
                    (StateStore.Store["CaptureRegion"] as GoodVariable<Rectangle>).ToString());*/
            sw.Close();
            fs.Close();

            // open the frame info stream
            fn = _currentMoviePath + @"\" + _currentMovieName + @" frames" + @".txt";
            _frameInfoFileSream = new FileStream(fn, FileMode.Create);
            _frameInfoStreamWriter = new StreamWriter(_frameInfoFileSream);
        }

        private void WriteFrame(Sample f)
        {
            Debug.Assert(f._objects.ContainsKey(_movieFrameElement.Value), "Frame does not contain movie element");

            Bitmap b;
            b = (Bitmap)f._objects[_movieFrameElement.Value];

            string frameName = _currentMovieName + @" Frame " + (_framesCaptured.Value).ToString() + @".bmp";
            string fullPath = _currentMoviePath + frameName;                                                   // frame name
            b.Save(fullPath);
            _frameInfoStreamWriter.WriteLine(frameName);

            lock (_framesToCapture)
                ++(_framesCaptured.Value);
        }

        private void EndMovieCapture()
        {
            Trace.WriteLine("Ending Movie Capture " + _currentMovieName);

            if (_frameInfoStreamWriter != null)
            {
                _frameInfoStreamWriter.Close();
                _frameInfoStreamWriter = null;
            }
            if (_frameInfoFileSream != null)
            {
                _frameInfoFileSream.Close();
                _frameInfoFileSream = null;
            }
            if (_autoincrementName.Value)                                // increase the movie number
                ++(_movieNumber.Value);

            _capturing.Set();
        }

        /// <summary>
        /// Event handler causes a movie capture to begin
        /// </summary>
        /// <param name="sender">Object requesting movie capture</param>
        /// <param name="args">Generic EventArgs</param>
        void StartCapture(object sender, EventArgs args)
        {
            // stop a capture in progress, if any
            StopCapture( this, new EventArgs());

            // create the directory, if needed
            _currentMovieName = _movieName.Value;
            if (_autoincrementName.Value)
                _currentMovieName += " " + (_movieNumber.Value).ToString();

            _currentMoviePath = _outputDirectory.Value + @"\" + _currentMovieName + @"\";

            if(!System.IO.Directory.Exists(_currentMoviePath))
                System.IO.Directory.CreateDirectory(_currentMoviePath);

            lock(_framesToCapture)
                _framesCaptured.Value = 0;

            Trace.WriteLine("MovieWriter.StartCaptue: beginning movie: " + _currentMovieName);
        }

        /// <summary>
        /// Event handler causes a movie capture in progress to stop.
        /// Blocks until capture has been stoppen or timed out.
        /// </summary>
        /// <param name="sender">Object requesting stop capture</param>
        /// <param name="args">Generic EventArgs</param>
        void StopCapture(object sender, EventArgs args)
        {
            if ( !_capturing.WaitOne(0, false))                                           // test to see if capturing
            {
                Trace.WriteLine("MovieWriter.StopCapture: ending movie " + _currentMovieName.ToString());

                _framesCaptured.Value = _framesToCapture.Value;

                // wait for acknowledgment from ThreadProc
                int timeout = (int)((GoodTimer)StateStore.Store["SamplePeriod"]).Value;
                bool b = _capturing.WaitOne(2 * timeout, false);

                Trace.WriteLineIf(b != false, "StopCapture: wait for movie to end timed out");
            }
        }

        #region IFrameProcessor Members

        public void ProcessFrame(object sender, SampleAcquiredEventArgs args)
        {
            _frameQueue.Enqueue(args.Frame);
        }

        public event FrameCompleteEventHandler FrameComplete;

        #endregion
    }
}
